package com.googlecode.contests_repo.servlets;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.codehaus.jackson.map.ObjectMapper;

import com.googlecode.contests_repo.core.WebTools;
import com.googlecode.contests_repo.core.model.*;
import com.googlecode.contests_repo.validation.ContestValidator;
import com.googlecode.contests_repo.validation.ValidationTools;

/**
 * Servlet implementation class ContestEditServlet
 */
@WebServlet("/ContestEditServlet")
public class ContestEditServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * Problem order message key
	 */
	public static final String PROBLEM_ORDER_KEY = "problem_order";

	/**
	 * Problem scores message key
	 */
	public static final String PROBLEM_SCORES_KEY = "problem_scores";

	/**
	 * Contest start time message key
	 */
	public static final String START_TIME_KEY = "start_time";

	/**
	 * Contest duration message key
	 */
	public static final String DURATION_KEY = "duration";

	/**
	 * Invalid scores array field key
	 */
	public static final String INVALID_SCORES_KEY = "invalid_scores";

	/**
	 * Invalid duration field key
	 */
	public static final String INVALID_DURATION_KEY = "invalid_duration";

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		List<String> errors = new ArrayList<String>();
		Map<String, String> errorMap = new HashMap<String, String>();
		String relativeContestPath = request.getParameter("path");
		Contest oldContest = getOldFieldData(request);
		Contest newContest = getNewFieldData(request, errorMap);
		Contest currentContest = getCurrentResource(relativeContestPath);

		if (!oldContest.getTitle().equals(newContest.getTitle())) {
			currentContest.setTitle(newContest.getTitle());
		}
		if (!oldContest.getAbout().equals(newContest.getAbout())) {
			currentContest.setAbout(newContest.getAbout());
		}
		if (!oldContest.getNotes().equals(newContest.getNotes())) {
			currentContest.setNotes(newContest.getNotes());
		}
		if (ContestValidator.isDateStringValid(newContest.getStartTime())) {
			if (!oldContest.getStartTime().equals(newContest.getStartTime())) {
				currentContest.setStartTime(newContest.getStartTime());
			}
		}
		if (newContest.getDuration() >= 0
				&& oldContest.getDuration() != newContest.getDuration()) {
			currentContest.setDuration(newContest.getDuration());
		}
		if (!oldContest.getGradingStyle().equals(newContest.getGradingStyle())) {
			currentContest.setGradingStyle(newContest.getGradingStyle());
		}
		if (!Arrays.equals(oldContest.getProblemOrder(),
				newContest.getProblemOrder())) {
			currentContest.setProblemOrder(newContest.getProblemOrder());
		}
		if (newContest.getProblemScores() != null
				&& !Arrays.equals(oldContest.getProblemScores(),
						newContest.getProblemScores())) {
			currentContest.setProblemScores(newContest.getProblemScores());
		}

		// validate
		validate(errors, errorMap, newContest, relativeContestPath);

		// save contest.json file
		if (errors.isEmpty() && errorMap.isEmpty()) {
			saveJson(errors, relativeContestPath, currentContest);
		} else {// some error occurred
			request.setAttribute("savedContest", newContest);
		}

		// send response
		request.setAttribute("errorMap", errorMap);
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println(WebTools.buildErrorString(errors, errorMap.size() < 1));
	}

	private void validate(List<String> errors, Map<String, String> errorMap,
			Contest newContest, String relativeContestPath) {
		if (!ContestValidator.isDateStringValid(newContest.getStartTime())) {
			errorMap.put(START_TIME_KEY, "Entered start time is invalid");
		}
		if (newContest.getDuration() < 0) {
			errorMap.put(DURATION_KEY, "Entered duration is invalid");
		}
		// validate contest.json
		errors.addAll(ValidationTools.validate(relativeContestPath,
				Contest.class));

		// validate problem order and score
		String problemMismatchMessage = ContestValidator
				.getListedProblemsDoNotExistMessage(relativeContestPath,
						newContest.getProblemOrder());
		if (!stringIsNullOrEmpty(problemMismatchMessage)) {
			errorMap.put(PROBLEM_ORDER_KEY, problemMismatchMessage);
		}

		String orderScoreSizeMessage = ContestValidator
				.getOrderScoreSizeMessage(relativeContestPath,
						newContest.getProblemOrder(),
						newContest.getProblemScores());
		if (!stringIsNullOrEmpty(orderScoreSizeMessage)) {
			errorMap.put(PROBLEM_SCORES_KEY, orderScoreSizeMessage);
		}

		String scoreNumberMessage = ContestValidator
				.getScoreNumberMismatchMessage(relativeContestPath,
						newContest.getProblemOrder(),
						newContest.getProblemScores());
		if (!stringIsNullOrEmpty(scoreNumberMessage)) {
			errors.add(scoreNumberMessage);
		}
	}

	private void saveJson(List<String> errors, String relativeContestPath,
			Contest currentContest) {
		ObjectMapper mapper = new ObjectMapper();
		String fullContestPath = ResourcesUtils
				.getFullResourcePath(relativeContestPath);
		try {
			mapper.writeValue(new File(fullContestPath, "contest.json"),
					currentContest);
		} catch (Exception e) {
			errors.add(e.getMessage());
		}
	}

	private Contest getNewFieldData(HttpServletRequest request,
			Map<String, String> errorMap) {
		Contest contest = new Contest();
		contest.setId(request.getParameter("id"));
		contest.setTitle(request.getParameter("title"));
		contest.setAbout(request.getParameter("about"));
		contest.setNotes(request.getParameter("notes"));
		contest.setStartTime(request.getParameter("start-time"));
		int duration = -1;
		try {
			duration = Integer.parseInt(request.getParameter("duration"));
		} catch (Exception e) {
			request.setAttribute(INVALID_DURATION_KEY,
					request.getParameter("duration"));
		}
		contest.setDuration(duration);
		contest.setGradingStyle(request.getParameter("grading-style"));
		String[] problemOrder = WebTools.stringToArray(request
				.getParameter("problem-order"));
		if (problemOrder == null) {
			problemOrder = new String[] {};
		}
		contest.setProblemOrder(problemOrder);
		int[] problemScores = null;
		try {
			problemScores = WebTools.integerStringToArray(request
					.getParameter("problem-scores"));
			if (problemScores == null) {
				problemScores = new int[] {};
			}
		} catch (Exception e) {
			if (errorMap != null) {
				errorMap.put(PROBLEM_SCORES_KEY,
						"Problem scores cannot be parsed: " + e.getMessage());
			}
			request.setAttribute(INVALID_SCORES_KEY,
					request.getParameter("problem-scores"));
		}
		contest.setProblemScores(problemScores);

		return contest;
	}

	private Contest getOldFieldData(HttpServletRequest request) {
		Contest contest = new Contest();
		contest.setId(request.getParameter("id"));
		contest.setTitle(request.getParameter("old-title"));
		contest.setAbout(request.getParameter("old-about"));
		contest.setNotes(request.getParameter("old-notes"));
		contest.setStartTime(request.getParameter("old-start-time"));
		int duration = 0;
		try {
			duration = Integer.parseInt(request.getParameter("old-duration"));
		} catch (Exception e) {
		}
		contest.setDuration(duration);
		contest.setGradingStyle(request.getParameter("old-grading-style"));
		String[] problemOrder = WebTools.stringToArray(request
				.getParameter("old-problem-order"));
		if (problemOrder == null) {
			problemOrder = new String[] {};
		}
		contest.setProblemOrder(problemOrder);
		int[] problemScores = WebTools.integerStringToArray(request
				.getParameter("old-problem-scores"));
		if (problemScores == null) {
			problemScores = new int[] {};
		}
		contest.setProblemScores(problemScores);
		return contest;
	}

	private Contest getCurrentResource(String relativeResourcePath) {
		String resourcePath = ResourcesUtils
				.getFullResourcePath(relativeResourcePath);

		return ResourcesUtils.getJSON(new File(resourcePath));
	}

	private static boolean stringIsNullOrEmpty(String s) {
		return s == null || "".equals(s);
	}
}
